import sys
import os
import re
from PyQt5.QtWidgets import (QApplication, QMainWindow, QScrollArea, QVBoxLayout, 
                             QWidget, QLabel, QScrollBar, QFileDialog, QMessageBox,
                             QHBoxLayout, QSlider, QPushButton, QComboBox, QDialog,
                             QListWidget, QDialogButtonBox, QLineEdit, QSplitter, QFrame)
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QPixmap, QImage, QFont, QIcon
from PIL import Image
import gc

# 支持的图片格式
SUPPORTED_FORMATS = {
    '.webp', '.jpg', '.jpeg', '.png', '.bmp', '.gif', 
    '.tiff', '.tif', '.ico', '.svg', '.pdf', '.ppm',
    '.pgm', '.pbm', '.pnm', '.dds', '.dib', '.eps'
}

class ImageSelectionDialog(QDialog):
    def __init__(self, image_paths, parent=None):
        super().__init__(parent)
        self.image_paths = image_paths
        self.selected_index = 0
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle('选择起始图片')
        self.setGeometry(200, 200, 800, 600)
        
        layout = QVBoxLayout(self)
        
        # 搜索框
        search_layout = QHBoxLayout()
        search_layout.addWidget(QLabel("搜索:"))
        self.search_input = QLineEdit()
        self.search_input.textChanged.connect(self.filter_images)
        search_layout.addWidget(self.search_input)
        layout.addLayout(search_layout)
        
        # 创建分割器
        splitter = QSplitter(Qt.Horizontal)
        
        # 图片列表
        self.image_list = QListWidget()
        self.image_list.itemDoubleClicked.connect(self.accept_selection)
        self.populate_image_list()
        splitter.addWidget(self.image_list)
        
        # 预览区域
        preview_widget = QWidget()
        preview_layout = QVBoxLayout(preview_widget)
        
        preview_layout.addWidget(QLabel("预览:"))
        self.preview_label = QLabel()
        self.preview_label.setAlignment(Qt.AlignCenter)
        self.preview_label.setMinimumSize(300, 300)
        self.preview_label.setStyleSheet("border: 1px solid #ccc; background-color: #f5f5f5;")
        self.preview_label.setText("选择图片预览")
        preview_layout.addWidget(self.preview_label)
        
        # 图片信息
        self.info_label = QLabel()
        self.info_label.setWordWrap(True)
        preview_layout.addWidget(self.info_label)
        
        splitter.addWidget(preview_widget)
        splitter.setSizes([400, 400])
        
        layout.addWidget(splitter)
        
        # 按钮
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept_selection)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)
        
        # 连接选择变化信号
        self.image_list.currentItemChanged.connect(self.on_selection_changed)
        
    def populate_image_list(self):
        """填充图片列表"""
        self.image_list.clear()
        for i, path in enumerate(self.image_paths):
            filename = os.path.basename(path)
            self.image_list.addItem(f"{i+1:03d}. {filename}")
    
    def filter_images(self):
        """根据搜索框内容过滤图片"""
        search_text = self.search_input.text().lower()
        
        for i in range(self.image_list.count()):
            item = self.image_list.item(i)
            item_text = item.text().lower()
            # 显示匹配的项目，隐藏不匹配的
            item.setHidden(search_text not in item_text and search_text != "")
    
    def on_selection_changed(self, current, previous):
        """当选择改变时更新预览"""
        if current is None:
            return
            
        index = self.image_list.row(current)
        if 0 <= index < len(self.image_paths):
            self.update_preview(index)
    
    def update_preview(self, index):
        """更新预览图片和信息"""
        try:
            image_path = self.image_paths[index]
            
            # 使用PIL打开图片获取信息
            with Image.open(image_path) as img:
                width, height = img.size
                mode = img.mode
                format_name = img.format
                size_kb = os.path.getsize(image_path) / 1024
                
            # 加载图片用于预览
            pil_image = Image.open(image_path)
            if pil_image.mode == 'RGB':
                qimage = QImage(pil_image.tobytes(), pil_image.width, pil_image.height, 
                               pil_image.width * 3, QImage.Format_RGB888)
            elif pil_image.mode == 'RGBA':
                qimage = QImage(pil_image.tobytes(), pil_image.width, pil_image.height, 
                               pil_image.width * 4, QImage.Format_RGBA8888)
            else:
                pil_image = pil_image.convert('RGB')
                qimage = QImage(pil_image.tobytes(), pil_image.width, pil_image.height, 
                               pil_image.width * 3, QImage.Format_RGB888)
            
            pixmap = QPixmap.fromImage(qimage)
            
            # 缩放预览图片以适应标签大小
            preview_size = self.preview_label.size()
            scaled_pixmap = pixmap.scaled(preview_size.width() - 20, preview_size.height() - 20, 
                                         Qt.KeepAspectRatio, Qt.SmoothTransformation)
            
            self.preview_label.setPixmap(scaled_pixmap)
            
            # 更新信息
            info_text = (f"文件名: {os.path.basename(image_path)}\n"
                        f"尺寸: {width} x {height}\n"
                        f"格式: {format_name} ({mode})\n"
                        f"大小: {size_kb:.1f} KB\n"
                        f"位置: {index+1}/{len(self.image_paths)}")
            self.info_label.setText(info_text)
            
        except Exception as e:
            self.preview_label.setText(f"预览加载失败\n{str(e)}")
            self.info_label.setText(f"错误: {str(e)}")
    
    def accept_selection(self):
        """接受选择"""
        current_row = self.image_list.currentRow()
        if current_row >= 0:
            self.selected_index = current_row
            self.accept()
        else:
            QMessageBox.warning(self, "警告", "请选择一张图片")

class UniversalImageViewer(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.current_images = []  # 存储当前显示的图片
        self.image_paths = []     # 图片路径列表
        self.current_index = 0    # 当前显示的起始索引
        self.is_loading = False   # 防止重复加载
        self.images_per_batch = 3 # 每批显示的图片数量
        self.scale_factor = 1.0   # 图片缩放因子
        self.fit_mode = "original" # 图片适应模式
        self.is_opening_folder = False  # 防止重复打开文件夹
        
    def initUI(self):
        self.setWindowTitle('通用图片查看器')
        self.setGeometry(100, 100, 1000, 700)
        
        # 创建中央部件和布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        
        # 创建控制面板
        self.create_control_panel(main_layout)
        
        # 创建滚动区域
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidgetResizable(True)
        self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.scroll_area.setStyleSheet("QScrollArea { border: none; }")
        
        # 创建用于显示图片的容器
        self.image_container = QWidget()
        self.container_layout = QVBoxLayout(self.image_container)
        self.container_layout.setAlignment(Qt.AlignTop)
        self.container_layout.setSpacing(0)  # 设置布局间距为0
        self.container_layout.setContentsMargins(0, 0, 0, 0)  # 设置边距为0
        
        self.scroll_area.setWidget(self.image_container)
        main_layout.addWidget(self.scroll_area)
        
        # 添加打开文件夹按钮
        self.open_btn = QLabel('点击打开图片文件夹')
        self.open_btn.setAlignment(Qt.AlignCenter)
        self.open_btn.setStyleSheet('''
            QLabel {
                background-color: #4CAF50;
                color: white;
                padding: 20px;
                font-size: 16px;
                border-radius: 10px;
                margin: 10px;
            }
            QLabel:hover {
                background-color: #45a049;
            }
        ''')
        self.open_btn.mousePressEvent = self.open_folder
        self.container_layout.addWidget(self.open_btn)
        
        # 加载延迟计时器
        self.load_timer = QTimer()
        self.load_timer.setSingleShot(True)
        # 只连接一次，不在每次打开文件夹时重新连接
        self.load_timer.timeout.connect(self.load_image_paths_delayed)
    
    def create_control_panel(self, parent_layout):
        """创建图片控制面板"""
        control_widget = QWidget()
        control_layout = QHBoxLayout(control_widget)
        
        # 缩放滑块
        control_layout.addWidget(QLabel("缩放:"))
        self.scale_slider = QSlider(Qt.Horizontal)
        self.scale_slider.setMinimum(10)  # 10%
        self.scale_slider.setMaximum(200) # 200%
        self.scale_slider.setValue(100)   # 100%
        self.scale_slider.setTickPosition(QSlider.TicksBelow)
        self.scale_slider.setTickInterval(10)
        self.scale_slider.valueChanged.connect(self.on_scale_changed)
        control_layout.addWidget(self.scale_slider)
        
        # 缩放百分比显示
        self.scale_label = QLabel("100%")
        self.scale_label.setMinimumWidth(50)
        control_layout.addWidget(self.scale_label)
        
        # 适应模式选择
        control_layout.addWidget(QLabel("适应模式:"))
        self.fit_combo = QComboBox()
        self.fit_combo.addItems(["原始大小", "适应宽度", "适应高度", "适应窗口"])
        self.fit_combo.currentTextChanged.connect(self.on_fit_mode_changed)
        control_layout.addWidget(self.fit_combo)
        
        # 重置按钮
        self.reset_btn = QPushButton("重置大小")
        self.reset_btn.clicked.connect(self.reset_scale)
        control_layout.addWidget(self.reset_btn)
        
        # 添加分隔符
        separator = QFrame()
        separator.setFrameShape(QFrame.VLine)
        separator.setFrameShadow(QFrame.Sunken)
        control_layout.addWidget(separator)
        
        # 批次大小控制
        control_layout.addWidget(QLabel("每批图片:"))
        self.batch_combo = QComboBox()
        # 添加更多选项，包括20和50
        self.batch_combo.addItems(["1", "3", "5", "10", "20", "50"])
        self.batch_combo.setCurrentText("3")
        self.batch_combo.currentTextChanged.connect(self.on_batch_size_changed)
        control_layout.addWidget(self.batch_combo)
        
        # 跳转按钮
        self.jump_btn = QPushButton("跳转到图片")
        self.jump_btn.clicked.connect(self.jump_to_image)
        self.jump_btn.setEnabled(False)  # 初始时禁用
        control_layout.addWidget(self.jump_btn)
        
        # 添加分隔符
        separator2 = QFrame()
        separator2.setFrameShape(QFrame.VLine)
        separator2.setFrameShadow(QFrame.Sunken)
        control_layout.addWidget(separator2)
        
        # 导航按钮 - 使用左右箭头，放在最右边
        self.prev_batch_btn = QPushButton("◀")
        self.prev_batch_btn.setToolTip("上一批")
        self.prev_batch_btn.clicked.connect(self.load_previous_batch)
        self.prev_batch_btn.setEnabled(False)
        self.prev_batch_btn.setFixedSize(40, 30)
        control_layout.addWidget(self.prev_batch_btn)
        
        self.next_batch_btn = QPushButton("▶")
        self.next_batch_btn.setToolTip("下一批")
        self.next_batch_btn.clicked.connect(self.load_next_batch)
        self.next_batch_btn.setEnabled(False)
        self.next_batch_btn.setFixedSize(40, 30)
        control_layout.addWidget(self.next_batch_btn)
        
        parent_layout.addWidget(control_widget)
    
    def load_previous_batch(self):
        """加载上一批图片"""
        if self.is_loading or self.current_index <= self.images_per_batch:
            return
            
        # 计算上一批的起始位置
        prev_start = max(0, self.current_index - 2 * self.images_per_batch)
        self.current_index = prev_start
        self.load_batch()
    
    def load_next_batch(self):
        """加载下一批图片"""
        if self.is_loading or self.current_index >= len(self.image_paths):
            return
            
        self.load_batch()
    
    def jump_to_image(self):
        """跳转到指定图片"""
        if not self.image_paths:
            return
            
        # 创建选择对话框
        dialog = ImageSelectionDialog(self.image_paths, self)
        if dialog.exec_() == QDialog.Accepted:
            selected_index = dialog.selected_index
            self.current_index = selected_index
            self.load_batch()
    
    def on_scale_changed(self, value):
        """缩放滑块值改变时的处理"""
        self.scale_factor = value / 100.0
        self.scale_label.setText(f"{value}%")
        self.update_all_images()
    
    def on_fit_mode_changed(self, mode_text):
        """适应模式改变时的处理"""
        mode_map = {
            "原始大小": "original",
            "适应宽度": "fit_width", 
            "适应高度": "fit_height",
            "适应窗口": "fit_window"
        }
        self.fit_mode = mode_map.get(mode_text, "original")
        self.update_all_images()
    
    def on_batch_size_changed(self, size_text):
        """批次大小改变时的处理"""
        old_batch_size = self.images_per_batch
        self.images_per_batch = int(size_text)
        
        # 重新加载当前图片，但保持当前位置
        if self.image_paths and self.current_images:
            # 计算当前显示的第一张图片的索引
            current_start_index = self.current_index - old_batch_size
            
            # 确保索引在有效范围内
            if current_start_index < 0:
                current_start_index = 0
            
            # 设置新的起始位置
            self.current_index = current_start_index
            
            # 重新加载当前批次
            self.load_batch()
    
    def reset_scale(self):
        """重置图片大小为原始大小"""
        self.scale_slider.setValue(100)
        self.fit_combo.setCurrentText("原始大小")
        self.update_all_images()
    
    def calculate_scaled_size(self, original_size, container_size=None):
        """根据当前设置计算图片缩放后的大小"""
        original_width, original_height = original_size
        
        if self.fit_mode == "original":
            # 原始大小乘以缩放因子
            width = int(original_width * self.scale_factor)
            height = int(original_height * self.scale_factor)
        elif self.fit_mode == "fit_width" and container_size:
            # 适应宽度
            max_width = container_size.width()  # 去除边距
            scale = max_width / original_width
            width = max_width
            height = int(original_height * scale * self.scale_factor)
        elif self.fit_mode == "fit_height" and container_size:
            # 适应高度 - 这里我们使用窗口高度的80%作为最大高度
            max_height = self.scroll_area.height() * 0.8
            scale = max_height / original_height
            width = int(original_width * scale * self.scale_factor)
            height = int(max_height * self.scale_factor)
        elif self.fit_mode == "fit_window" and container_size:
            # 适应窗口
            max_width = container_size.width()  # 去除边距
            max_height = self.scroll_area.height() * 0.8
            
            width_scale = max_width / original_width
            height_scale = max_height / original_height
            scale = min(width_scale, height_scale)
            
            width = int(original_width * scale * self.scale_factor)
            height = int(original_height * scale * self.scale_factor)
        else:
            # 默认使用原始大小乘以缩放因子
            width = int(original_width * self.scale_factor)
            height = int(original_height * self.scale_factor)
        
        return width, height
    
    def update_all_images(self):
        """更新所有当前显示的图片大小"""
        if not self.current_images:
            return
            
        container_size = self.scroll_area.size()
        
        for image_data in self.current_images:
            if 'original_pixmap' not in image_data:
                continue
                
            original_pixmap = image_data['original_pixmap']
            original_size = (original_pixmap.width(), original_pixmap.height())
            
            # 计算缩放后的大小
            scaled_width, scaled_height = self.calculate_scaled_size(original_size, container_size)
            
            # 缩放图片
            scaled_pixmap = original_pixmap.scaled(
                scaled_width, scaled_height, 
                Qt.KeepAspectRatio, 
                Qt.SmoothTransformation
            )
            
            # 更新显示的图片
            image_data['widget'].setPixmap(scaled_pixmap)
            image_data['pixmap'] = scaled_pixmap
            
        # 强制刷新UI
        QApplication.processEvents()
    
    def natural_sort_key(self, text):
        """
        自然排序键函数，确保1,2,3...10的顺序而不是1,10,11,2,3...
        """
        def convert(text):
            return int(text) if text.isdigit() else text.lower()
        
        return [convert(c) for c in re.split('([0-9]+)', text)]
    
    def is_image_file(self, filename):
        """检查文件是否为支持的图片格式"""
        ext = os.path.splitext(filename)[1].lower()
        return ext in SUPPORTED_FORMATS
    
    def open_folder(self, event):
        """打开文件夹选择图片"""
        if self.is_opening_folder:
            return
            
        self.is_opening_folder = True
        folder_path = QFileDialog.getExistingDirectory(self, '选择图片文件夹')
        
        if folder_path:
            # 显示加载中提示
            self.show_loading_message()
            
            # 设置要加载的文件夹路径
            self.folder_to_load = folder_path
            
            # 启动延迟加载计时器
            self.load_timer.start(500)  # 延迟500ms
        else:
            self.is_opening_folder = False
    
    def load_image_paths_delayed(self):
        """延迟加载图片路径"""
        if hasattr(self, 'folder_to_load'):
            self.load_image_paths(self.folder_to_load)
            delattr(self, 'folder_to_load')
        self.is_opening_folder = False
            
    def show_loading_message(self):
        """显示加载中消息"""
        self.clear_images()
        loading_label = QLabel('正在加载图片，请稍候...')
        loading_label.setAlignment(Qt.AlignCenter)
        loading_label.setStyleSheet('font-size: 16px; color: #666; padding: 20px;')
        self.container_layout.addWidget(loading_label)
        QApplication.processEvents()  # 强制刷新UI
        
    def load_image_paths(self, folder_path):
        """加载文件夹中的所有图片"""
        try:
            self.image_paths = []
            for file in os.listdir(folder_path):
                if self.is_image_file(file):
                    self.image_paths.append(os.path.join(folder_path, file))
            
            if not self.image_paths:
                QMessageBox.warning(self, '提示', f'未找到支持的图片文件！\n支持的格式: {", ".join(sorted(SUPPORTED_FORMATS))}')
                self.show_open_button()
                return
                
            # 使用自然排序而不是简单的字符串排序
            self.image_paths.sort(key=lambda x: self.natural_sort_key(os.path.basename(x)))
            
            print(f"找到 {len(self.image_paths)} 张图片，排序后:")
            for i, path in enumerate(self.image_paths[:10]):  # 只打印前10个
                print(f"  {i+1}: {os.path.basename(path)}")
            if len(self.image_paths) > 10:
                print(f"  ... 还有 {len(self.image_paths)-10} 张图片")
            
            # 启用跳转按钮和导航按钮
            self.jump_btn.setEnabled(True)
            self.prev_batch_btn.setEnabled(True)
            self.next_batch_btn.setEnabled(True)
            
            # 弹出选择起始图片的对话框
            self.prompt_start_image()
            
        except Exception as e:
            QMessageBox.critical(self, '错误', f'加载图片失败: {str(e)}')
            self.show_open_button()
        finally:
            self.is_opening_folder = False
    
    def prompt_start_image(self):
        """提示用户选择起始图片"""
        dialog = ImageSelectionDialog(self.image_paths, self)
        dialog.setWindowTitle("选择起始图片")
        
        if dialog.exec_() == QDialog.Accepted:
            self.current_index = dialog.selected_index
            print(f"从第 {self.current_index + 1} 张图片开始: {os.path.basename(self.image_paths[self.current_index])}")
        else:
            # 如果用户取消，默认从第一张开始
            self.current_index = 0
            print("使用默认起始位置: 第1张图片")
        
        # 清空当前显示
        self.clear_images()
        
        # 初始加载第一批图片
        self.load_batch()
    
    def show_open_button(self):
        """显示打开文件夹按钮"""
        self.open_btn.show()
            
    def clear_images(self):
        """清空所有图片"""
        for i in reversed(range(self.container_layout.count())):
            widget = self.container_layout.itemAt(i).widget()
            if widget and widget != self.open_btn:
                widget.deleteLater()
        self.current_images.clear()
        gc.collect()  # 强制垃圾回收
        
    def load_batch(self):
        """加载当前批次的图片"""
        if self.is_loading or self.current_index >= len(self.image_paths):
            return
            
        self.is_loading = True
        
        try:
            # 清空当前显示的图片
            self.clear_images()
            
            # 计算本批次要加载的图片范围
            start_index = self.current_index
            end_index = min(self.current_index + self.images_per_batch, len(self.image_paths))
            
            # 加载本批次图片
            for i in range(start_index, end_index):
                image_path = self.image_paths[i]
                
                try:
                    # 使用PIL打开图片
                    pil_image = Image.open(image_path)
                    
                    # 转换为QImage
                    if pil_image.mode == 'RGB':
                        qimage = QImage(pil_image.tobytes(), pil_image.width, pil_image.height, 
                                       pil_image.width * 3, QImage.Format_RGB888)
                    elif pil_image.mode == 'RGBA':
                        qimage = QImage(pil_image.tobytes(), pil_image.width, pil_image.height, 
                                       pil_image.width * 4, QImage.Format_RGBA8888)
                    else:
                        pil_image = pil_image.convert('RGB')
                        qimage = QImage(pil_image.tobytes(), pil_image.width, pil_image.height, 
                                       pil_image.width * 3, QImage.Format_RGB888)
                    
                    original_pixmap = QPixmap.fromImage(qimage)
                    
                    # 根据当前设置计算缩放后的大小
                    container_size = self.scroll_area.size()
                    original_size = (original_pixmap.width(), original_pixmap.height())
                    scaled_width, scaled_height = self.calculate_scaled_size(original_size, container_size)
                    
                    # 缩放图片
                    scaled_pixmap = original_pixmap.scaled(
                        scaled_width, scaled_height, 
                        Qt.KeepAspectRatio, 
                        Qt.SmoothTransformation
                    )
                    
                    # 创建图片标签
                    image_label = QLabel()
                    image_label.setPixmap(scaled_pixmap)
                    image_label.setAlignment(Qt.AlignCenter)
                    image_label.setStyleSheet('margin: 0px; border: 0px;')  # 去除边距和边框
                    
                    # 添加到布局
                    self.container_layout.addWidget(image_label)
                    self.current_images.append({
                        'widget': image_label,
                        'pixmap': scaled_pixmap,
                        'original_pixmap': original_pixmap,  # 保存原始图片用于重新缩放
                        'path': image_path
                    })
                    
                    print(f'加载图片 {i+1}/{len(self.image_paths)}: {os.path.basename(image_path)}')
                    
                    # 处理UI事件，确保图片能及时显示
                    QApplication.processEvents()
                    
                except Exception as e:
                    print(f'加载图片失败 {image_path}: {str(e)}')
                    continue
            
            # 更新当前索引
            self.current_index = end_index
            
            # 更新导航按钮状态
            self.update_navigation_buttons()
            
            # 显示批次信息
            total_batches = (len(self.image_paths) + self.images_per_batch - 1) // self.images_per_batch
            current_batch = (start_index // self.images_per_batch) + 1
            
            batch_info = QLabel(f'批次: {current_batch}/{total_batches} (图片 {start_index+1}-{end_index}/{len(self.image_paths)})')
            batch_info.setAlignment(Qt.AlignCenter)
            batch_info.setStyleSheet('font-size: 14px; color: #666; padding: 10px;')
            self.container_layout.addWidget(batch_info)
            
            # 滚动到顶部
            QTimer.singleShot(100, self.scroll_to_top)
            
        finally:
            self.is_loading = False
    
    def update_navigation_buttons(self):
        """更新导航按钮状态"""
        # 上一批按钮：如果当前不是第一批，则启用
        self.prev_batch_btn.setEnabled(self.current_index > self.images_per_batch)
        
        # 下一批按钮：如果还有更多图片，则启用
        self.next_batch_btn.setEnabled(self.current_index < len(self.image_paths))
            
    def scroll_to_top(self):
        """滚动到顶部"""
        self.scroll_area.verticalScrollBar().setValue(0)
    
    def resizeEvent(self, event):
        """窗口大小改变事件"""
        super().resizeEvent(event)
        # 如果适应模式是适应窗口，则在窗口大小改变时更新图片大小
        if self.fit_mode == "fit_window" and self.current_images:
            self.update_all_images()

def main():
    app = QApplication(sys.argv)
    
    # 设置应用信息
    app.setApplicationName('通用图片查看器')
    app.setApplicationVersion('1.0')
    
    viewer = UniversalImageViewer()
    viewer.show()
    
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()